/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: GammuStateMachineCL.java 658 2007-06-30 08:59:11Z jack.rybicki $
 */
package gammu.java.machines;

import gammu.java.GSMErrorException;
import gammu.java.GammuObserver;
import gammu.java.GammuStateMachine;
import gammu.java.OutgoingSMS;

import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.PrintWriter;
import java.text.StringCharacterIterator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.NotImplementedException;

/**
 * The class runs an external "gammu" program and collects output.
 * 
 * 
 */
public class GammuStateMachineCL extends GammuStateMachine {

	private Process process;

	private boolean monitoredCall = false;

	private boolean monitoredSMS = false;

	private boolean monitoredUSSD = false;

	private boolean monitoredCB = false;

	private boolean monitorRunning = false;

	private int configSection = 0;

	public GammuStateMachineCL() {
	}

	private boolean monitoringEnabled() {
		return monitoredCall || monitoredSMS || monitoredUSSD || monitoredCB;
	}

	/**
	 * @see gammu.java.GammuStateMachine#answerCall()
	 */
	@Override
	public void answerCall() {
		process = CLUtils.execute("gammu", String.valueOf(configSection), "--answercall");
		InputStream is = process.getInputStream();
		try {
			List<String> l = CLUtils.readLines(is, null, 0, 2000);
			String last = l.size() < 1 ? "" : l.get(l.size() - 1);
			verifyOutput(last);
		} catch (InterruptedIOException e) {
			throw GSMErrorException.ERR_TIMEOUT;
		} catch (IOException e) {
			throw GSMErrorException.ERR_UNKNOWN;
		}
	}

	/**
	 * @see gammu.java.GammuStateMachine#cancelCall()
	 */
	@Override
	public void cancelCall() {
		process = CLUtils.execute("gammu", String.valueOf(configSection), "--cancelcall");
		InputStream is = process.getInputStream();
		try {
			List<String> l = CLUtils.readLines(is, null, 0, 2000);
			String last = l.size() < 1 ? "" : l.get(l.size() - 1);
			verifyOutput(last);
		} catch (InterruptedIOException e) {
			throw GSMErrorException.ERR_TIMEOUT;
		} catch (IOException e) {
			throw GSMErrorException.ERR_UNKNOWN;
		}
	}

	/**
	 * @see gammu.java.GammuStateMachine#dialVoice(java.lang.String, boolean)
	 */
	@Override
	public int dialVoice(String number, ShowNumber showNumber) {
		if (showNumber == ShowNumber.DEFAULT) {
			process = CLUtils.execute("gammu", String.valueOf(configSection), "--dialvoice", String
					.valueOf(number));
		} else {
			process = CLUtils.execute("gammu", String.valueOf(configSection), "--dialvoice", String
					.valueOf(number), showNumber == ShowNumber.SHOW ? "show"
					: "hide");
		}
		InputStream is = process.getInputStream();
		try {
			List<String> l = CLUtils.readLines(is, null, 0, 2000);
			String last = l.size() < 1 ? "" : l.get(l.size() - 1);
			verifyOutput(last);
		} catch (InterruptedIOException e) {
			throw GSMErrorException.ERR_TIMEOUT;
		} catch (IOException e) {
			throw GSMErrorException.ERR_UNKNOWN;
		}
		return 1;
	}

	/**
	 * @see gammu.java.GammuStateMachine#getAllNetworks()
	 */
	@Override
	public List<String> getAllNetworks() {
		process = CLUtils.execute("gammu", String.valueOf(configSection), "--listallnetworks");
		InputStream is = process.getInputStream();
		try {
			List<String> l = CLUtils.readLines(is, null, 0, 2000);
			String last = l.size() < 1 ? "" : l.get(l.size() - 1);
			verifyOutput(last);
			return l;
		} catch (InterruptedIOException e) {
			throw GSMErrorException.ERR_TIMEOUT;
		} catch (IOException e) {
			throw GSMErrorException.ERR_UNKNOWN;
		}
	}

	/**
	 * @see gammu.java.GammuStateMachine#getBatteryInfo()
	 */
	@Override
	public Map<String, String> getBatteryInfo() {
		process = CLUtils.execute("gammu", String.valueOf(configSection), "--monitor", "1");
		InputStream is = process.getInputStream();
		try {
			List<String> l = CLUtils.readLines(is, null, 0, 2000);
			String last = l.size() < 1 ? "" : l.get(l.size() - 1);
			verifyOutput(last);
			Map<String, String> result = new HashMap<String, String>(10);
			for (String s : l) {
				String slc = s.toLowerCase();
				if (slc.contains("battery") || slc.contains("current")
						|| slc.contains("temperature")
						|| slc.contains("charge")) {
					String[] val = s.split(":");
					if (val.length > 1) {
						result.put(val[0].trim(), val[1].trim());
					}
				}
			}
			return result;
		} catch (InterruptedIOException e) {
			throw GSMErrorException.ERR_TIMEOUT;
		} catch (IOException e) {
			throw GSMErrorException.ERR_UNKNOWN;
		}
	}

	/**
	 * @see gammu.java.GammuStateMachine#getNetworkInfo()
	 */
	@Override
	public Map<String, String> getNetworkInfo() {
		process = CLUtils.execute("gammu", String.valueOf(configSection), "--monitor", "1");
		InputStream is = process.getInputStream();
		try {
			List<String> l = CLUtils.readLines(is, null, 0, 2000);
			String last = l.size() < 1 ? "" : l.get(l.size() - 1);
			verifyOutput(last);
			Map<String, String> result = new HashMap<String, String>(5);
			for (String s : l) {
				if (s.toLowerCase().contains("network")
						|| s.toLowerCase().contains("signal")) {
					String[] val = s.split(":");
					if (val.length > 1) {
						result.put(val[0].trim(), val[1].trim());
					}
				}
			}
			return result;
		} catch (InterruptedIOException e) {
			throw GSMErrorException.ERR_TIMEOUT;
		} catch (IOException e) {
			throw GSMErrorException.ERR_UNKNOWN;
		}
	}

	/**
	 * @see gammu.java.GammuStateMachine#getPhoneId()
	 */
	@Override
	public Map<String, String> getPhoneId() {
		process = CLUtils.execute("gammu", String.valueOf(configSection), "--identify");
		InputStream is = process.getInputStream();
		try {
			List<String> l = CLUtils.readLines(is, null, 0, 2000);
			String last = l.size() < 1 ? "" : l.get(l.size() - 1);
			verifyOutput(last);
			Map<String, String> result = new HashMap<String, String>(6);
			for (String s : l) {
				String[] val = s.split(":");
				if (val.length > 1) {
					result.put(val[0].trim(), val[1].trim());
				}
			}
			return result;
		} catch (InterruptedIOException e) {
			throw GSMErrorException.ERR_TIMEOUT;
		} catch (IOException e) {
			throw GSMErrorException.ERR_UNKNOWN;
		}
	}

	/**
	 * @see gammu.java.GammuStateMachine#getSMS(java.lang.String, int)
	 */
	@Override
	public Map<String, String> getSMS(String folder, int location) {
		throw new NotImplementedException();
	}

	/**
	 * @see gammu.java.GammuStateMachine#getSMSC(int)
	 */
	@Override
	public Map<String, String> getSMSC(int location) {
		process = CLUtils.execute("gammu", String.valueOf(configSection), "--getsmsc", String
				.valueOf(location));
		InputStream is = process.getInputStream();
		try {
			List<String> l = CLUtils.readLines(is, null, 0, 2000);
			String last = l.size() < 1 ? "" : l.get(l.size() - 1);
			verifyOutput(last);
			if (last.startsWith("Invalid location")
					|| last.contains("enumerate locations from")) {
				throw GSMErrorException.ERR_INVALIDSMSCID;
			}
			Map<String, String> result = new HashMap<String, String>(6);
			for (String s : l) {
				String[] val = s.split(":");
				if (val.length > 1) {
					result.put(val[0].trim(), val[1].trim());
				}
			}
			return result;
		} catch (InterruptedIOException e) {
			throw GSMErrorException.ERR_TIMEOUT;
		} catch (IOException e) {
			throw GSMErrorException.ERR_UNKNOWN;
		}
	}

	/**
	 * @see gammu.java.GammuStateMachine#init()
	 */
	@Override
	public void init() {
		process = CLUtils.execute("gammu", String.valueOf(configSection), "--identify");
		InputStream is = process.getInputStream();
		try {
			List<String> l = CLUtils.readLines(is, null, 0, 2000);
			String last = l.size() < 1 ? "" : l.get(l.size() - 1);
			verifyOutput(last);
		} catch (InterruptedIOException e) {
			throw GSMErrorException.ERR_TIMEOUT;
		} catch (IOException e) {
			throw GSMErrorException.ERR_UNKNOWN;
		}
	}

	/**
	 * @see gammu.java.GammuStateMachine#pressKey(char, boolean)
	 */
	@Override
	public void pressKey(char key, boolean press) {
		process = CLUtils.execute("gammu", String.valueOf(configSection), "--presskeysequence", String
				.valueOf(key));
		InputStream is = process.getInputStream();
		try {
			List<String> l = CLUtils.readLines(is, null, 0, 2000);
			String last = l.size() < 1 ? "" : l.get(l.size() - 1);
			verifyOutput(last);
			if (last.startsWith("Unknown key/function")) {
				throw GSMErrorException.ERR_NOTSUPPORTEDPARAMETER;
			}
		} catch (InterruptedIOException e) {
			throw GSMErrorException.ERR_TIMEOUT;
		} catch (IOException e) {
			throw GSMErrorException.ERR_UNKNOWN;
		}
	}

	/**
	 * @see gammu.java.GammuStateMachine#pressKeySequence(java.lang.String)
	 */
	@Override
	public void pressKeySequence(String keys) {
		StringCharacterIterator it = new StringCharacterIterator(keys);
		for (char c = it.first(); c != StringCharacterIterator.DONE; c = it
				.next()) {
			pressKey(c, true);
		}
	}

	/**
	 * @see gammu.java.GammuStateMachine#readConfig(int)
	 */
	@Override
	public void readConfig(int section) {
		if (section < 0) {
			throw GSMErrorException.ERR_INVALIDCONFIG;
		}
		this.configSection = section;
	}

	/**
	 * @see gammu.java.GammuStateMachine#sendSMS(java.util.Map)
	 */
	@Override
	public void sendSMS(OutgoingSMS value) {
		process = CLUtils.execute("gammu", String.valueOf(configSection), "--sendsms", "TEXT", value
				.getRemoteNumber());
		PrintWriter w = new PrintWriter(process.getOutputStream());
		w.println(value.getText());
		w.flush();
		w.close();
		InputStream is = process.getInputStream();
		try {
			List<String> l = CLUtils.readLines(is, null, 0, 2000);
			String last = l.size() < 1 ? "" : l.get(l.size() - 1);
			verifyOutput(last);
		} catch (InterruptedIOException e) {
			throw GSMErrorException.ERR_TIMEOUT;
		} catch (IOException e) {
			throw GSMErrorException.ERR_UNKNOWN;
		}
	}

	/**
	 * @see gammu.java.GammuStateMachine#setConfig(java.util.Map)
	 */
	@Override
	public void setConfig(Map attributes) {
		throw new NotImplementedException();
	}

	/**
	 * @see gammu.java.GammuStateMachine#setIncomingCall(boolean)
	 */
	@Override
	public void setIncomingCall(boolean enable) {
		monitoredCall = enable;
		if (!monitorRunning && monitoringEnabled()) {
			startMonitor();
		}
	}

	private void startMonitor() {
		(new Thread() {
			public void run() {
				process = CLUtils.execute("gammu", String.valueOf(configSection), "--monitor", "3");
			}
		}).start();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
		}
		InputStream is = process.getInputStream();
		try {
			List<String> l = CLUtils.readLines(is, "incoming call from", 0, 0);
			String last = l.size() < 1 ? "" : l.get(l.size() - 1);
			verifyOutput(last);
			for (String s : l) {
				System.out.println(s);
			}

		} catch (InterruptedIOException e) {
			throw GSMErrorException.ERR_TIMEOUT;
		} catch (IOException e) {
			throw GSMErrorException.ERR_UNKNOWN;
		}

	}

	/**
	 * @see gammu.java.GammuStateMachine#setIncmingCB(boolean)
	 */
	@Override
	public void setIncmingCB(boolean enable) {
		throw new NotImplementedException();
	}

	/**
	 * @see gammu.java.GammuStateMachine#setIncomingSMS(boolean)
	 */
	@Override
	public void setIncomingSMS(boolean enable) {
		throw new NotImplementedException();
	}

	/**
	 * @see gammu.java.GammuStateMachine#setIncomingUSSD(boolean)
	 */
	@Override
	public void setIncomingUSSD(boolean enable) {
		throw new NotImplementedException();
	}

	/**
	 * @see gammu.java.GammuStateMachine#terminate()
	 */
	@Override
	public void terminate() {
		// nothing to do
	}

	private static void verifyOutput(String s) {
		if (s.startsWith("Function not supported by phone.")) {
			throw GSMErrorException.ERR_NOTSUPPORTED;
		} else if (s.startsWith("Unknown error.")) {
			throw GSMErrorException.ERR_UNKNOWN;
		} else if (s.startsWith("No response in specified timeout")) {
			throw GSMErrorException.ERR_NOTCONNECTED;
		}
	}
}
